﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Media;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Xml.Serialization;

namespace ClientApplication
{
    /// <summary>
    /// The Client-class which handles all kommunication with the server via "UdpClient".
    /// </summary>
    class Client : IClient
    {
        /// <summary>
        /// Local variables and events
        /// </summary>
        int loginPort;
        int standardPort;
        string id = "";
        string password = "";
        string serverIP;
        public bool isJudge = false;
        public bool isAdmin = false;
        Byte[] sendingPacket;
        Byte[] recievedPacket;
        public IPEndPoint serverStandardIpEp;
        public ContestPacket activeContest;
        UdpClient client;
        public event JudgingMessageEventHandler GiveJudgingMessageToJudgeForm = null;
        public event MessageEventHandler UpdateJudgeTextBoxGreen = null;
        public event MessageEventHandler GiveMessageToAdminForm = null;
        public event CurrentJumpEventHandler GiveActiveJumpToAdminForm = null;
        public event MessageEventHandler GiveGeneralMessageToJudgeForm = null;
        public event ResponseEventHandler LoggedIn = null;
        public event ConnectedJudgesEventHandler RefreshConnectedJudges = null;
        public event MessageEventHandler GiveNewContestantMessageToAdminForm = null;
        public event StringListEventHandler GiveFinishedContestsToAdminForm = null;
        public event ContestPacketEventHandler GiveContestPacketToAdminForm = null;
        public event ContestPacketEventHandler GiveActiveContestPacketToAdminForm = null;
        public event MessageEventHandler UpdateJudgeTextBoxRed = null;
        public event MessageEventHandler UpdateJudgeTextBoxWhite = null;
        public event GeneralEventHandler ResetEndedCompetition = null;

        /// <summary>
        /// Constructor with parameters
        /// </summary>
        /// <param name="serverIP"></param>
        /// <param name="standardPort"></param>
        /// <param name="loginPort"></param>
        public Client(string serverIP, int standardPort, int loginPort)
        {
            this.serverIP = serverIP;
            this.standardPort = standardPort;
            this.loginPort = loginPort;
            this.serverStandardIpEp = new IPEndPoint(IPAddress.Parse(serverIP), loginPort);
            this.client = new UdpClient(9054);
        }

        /// <summary>
        /// Send-methods that sends various messages to the server, often as xml-serialized strings
        /// </summary>
        /// <param name="UdpClient"></param>
        /// <param name="message"></param>
        /// <param name="IpEndPoint"></param>
        #region Send-Methods
        public void SendMessage(UdpClient UdpClient, string message, IPEndPoint IpEndPoint)
        {
            sendingPacket = new Byte[1024];
            sendingPacket = Encoding.ASCII.GetBytes(message);
            UdpClient.Send(sendingPacket, sendingPacket.Length, IpEndPoint);
        }

        public void SendJumpCode(string jumpCode)
        {
            string message = "1@" + this.id + "@" + jumpCode;
            SendMessage(client, message, serverStandardIpEp);
        }

        public void SendingScore(string score)
        {
            if (isJudge)
            {
                score = "1" + '@' + id + '@' + score;
     
                SendMessage(client, score, serverStandardIpEp);
            }
        }

        public void SendRulesAsXML(SetupRulesArgs e)
        {
            if (isAdmin)
            {
                string sRules = string.Empty;
                Rules rules = new Rules(e.GetHeight, e.GetAmountJudges, e.GetSex, e.GetDate, e.GetPlace);

                XmlSerializer writer = new XmlSerializer(rules.GetType());
                using (System.IO.StringWriter file = new System.IO.StringWriter())
                {
                    writer.Serialize(file, rules);
                    sRules = file.ToString();
                }
                SendMessage(client, "4@" + id + "@2$" + sRules, serverStandardIpEp);
            }
        }

        public void SendContestantsToServer(CopyContestantsEventArgs e)
        {
            if (isAdmin)
            {
                
                //********FÖR ATT SKRIVA TILL FIL**********
                //string filePath = @"c:\contestants.xml";
                //System.Xml.Serialization.XmlSerializer writer =
                //    new System.Xml.Serialization.XmlSerializer(typeof(ContestantList));

                //System.IO.StreamWriter file = new System.IO.StreamWriter(
                //    filePath);
                //writer.Serialize(file, e.cList);
                //file.Close();

                //********FÖR ATT SKICKA TILL SERVER*********
                string sCList = string.Empty;
                XmlSerializer writer = new XmlSerializer(e.cList.GetType());
                using (System.IO.StringWriter file = new System.IO.StringWriter())
                {
                    writer.Serialize(file, e.cList);
                    sCList = file.ToString();
                }
                SendMessage(client, "4@" + id + "@3$" + sCList, serverStandardIpEp);
            }
        }

        public void SendChosenJudgesToServer(ChosenJudgeEventArgs e)
        {
            if (isAdmin)
            {
                string sJList = string.Empty;

                XmlSerializer writer = new XmlSerializer(e.jList.GetType());
                using (System.IO.StringWriter file = new System.IO.StringWriter())
                {
                    writer.Serialize(file, e.jList);
                    sJList = file.ToString();
                }
                SendMessage(client, "4@" + id + "@4$" + sJList, serverStandardIpEp);
            }
        }

        public void SendFinishedContestsRequest()
        {
            SendMessage(client, "4@" + id + "@5", serverStandardIpEp);
        }

        public void SendViewContestRequest(Guid guid)
        {
            SendMessage(client, "4@" + id + "@6&" + guid.ToString(), serverStandardIpEp);
        }

        public void SendConnectedJudgesRefresh()
        {
            while (isAdmin)
            {
                SendMessage(client, "4@" + id + "@1", serverStandardIpEp);
                System.Threading.Thread.Sleep(1000);
            }
        }

        public void SendJudgeOfflineReply(List<Judge> jList)
        {
            if (jList.Count == 0)
            {
                SendMessage(client, "Wait", serverStandardIpEp);
            }
            else
            {
                SendMessage(client, "NewJudges@" + Serialize<List<Judge>>(jList), serverStandardIpEp);
            }
        }
        #endregion

        /// <summary>
        /// Listens to server and returns the message string
        /// </summary>
        /// <param name="udpClient"></param>
        /// <param name="sender"></param>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public string GetMessage(UdpClient udpClient,  ref IPEndPoint sender, int timeout = 0)
        {
            recievedPacket = new byte[1024];

            Console.WriteLine("Waiting for connection.");
            udpClient.Client.ReceiveTimeout = timeout;
            
            try
            {
                recievedPacket = udpClient.Receive(ref sender);
                Console.WriteLine("Message recieved from {0}", sender.ToString());

                string message = Encoding.ASCII.GetString(recievedPacket);

                Console.WriteLine(message);
                Console.WriteLine("Sender adress: " + sender.Address + " Sender port:" + sender.Port);

                return message;
            }
            catch
            {
                return "Server not responding@@";
            } 
        }

        /// <summary>
        /// Handles the login messages and sets whether the client is admin or judge
        /// </summary>
        /// <param name="id"></param>
        /// <param name="password"></param>
        /// <param name="serverIP"></param>
        /// <param name="timeout"></param>
        public void Login(string id, string password, IPAddress serverIP, int timeout)
        {
            string logInMessage = "3@" + id + "@" + password + '@';
            Message loginResponse;
            serverStandardIpEp.Address = serverIP;
            SendMessage(client, logInMessage, serverStandardIpEp);
            IPEndPoint sender = new IPEndPoint(serverStandardIpEp.Address, loginPort);
            while (true)
            {
                loginResponse = new Message(GetMessage(client, ref sender, timeout), sender);
                if (loginResponse.type == "LogIn") 
                    break;
                else if (loginResponse.type == "Server not responding")
                {
                    MessageBox.Show("Server not responding");
                    break;
                }
            }
            //If the login is saved as a audge-client, the client gets the response "Judge" and gets logged in as a judge.
            if (loginResponse.message == "Judge")
            {
                ResponseEventArgs e = new ResponseEventArgs(2);
                if (this.LoggedIn != null)
                {
                    this.LoggedIn(this, e);
                }
                this.id = id;
                this.password = password;
                isJudge = true;
                isAdmin = false;
                try
                {
                    SoundPlayer player = new SoundPlayer(Path.GetFullPath("Happyjump.wav"));
                    player.Play();
                }
                catch{}
            }

            ///If the login is saved as a admin-client, the client gets the response "Admin" and gets logged in as a admin.
            else if (loginResponse.message == "Admin")
            {
                ResponseEventArgs e = new ResponseEventArgs(1);
                if (this.LoggedIn != null)
                {
                    this.LoggedIn(this, e);
                }
                this.id = id;
                this.password = password;
                isAdmin = true;
                isJudge = false;
                Thread RefreshJudges = new Thread(SendConnectedJudgesRefresh);
                RefreshJudges.IsBackground = true;
                RefreshJudges.Start();
            }

            else if (loginResponse.message == "Server not responding")
            {
                MessageBox.Show("Server not responding");
            }

            ///If the recent cases in the login is not performed, there has probably been a wrong ID or password.
            else if (loginResponse.message == "NotValid")
            {
                MessageBox.Show("Wrong ID or Password");
            }
        }

        public void GettingAndHandlingMessagesJudge()
        {
            while (isJudge)
            {
                string[] split;
                string contestantID;
                string jumpCode;
                string jumpNr;
                IPEndPoint senderIpEp = new IPEndPoint(IPAddress.Any, 0);
                Message serverMessage = new Message(GetMessage(client, ref senderIpEp), senderIpEp); //Here the listening gets called
                if (serverMessage.type == "ACK") //Clears screen when ackknowledge is recieved from the server.
                {
                    if (this.GiveGeneralMessageToJudgeForm != null)
                    {
                        this.GiveGeneralMessageToJudgeForm(this, "ACK@ Score " + serverMessage.message + " recieved by server");
                    }
                }

                else if (serverMessage.type == "NewJumpInfo")
                {
                    try
                    {
                        split = serverMessage.message.Split('&');
                        contestantID = split[0];
                        jumpCode = split[1];
                        jumpNr = split[2];
                        JudgingMessageEventArgs e = new JudgingMessageEventArgs(contestantID, jumpCode, jumpNr);
                        if (this.GiveJudgingMessageToJudgeForm != null)
                            this.GiveJudgingMessageToJudgeForm(this, e);
                    }
                    catch
                    {
                        if(this.GiveGeneralMessageToJudgeForm != null)
                            this.GiveGeneralMessageToJudgeForm(this, "Could not handle jumpmessage from server");
                    }
                }

                else if(serverMessage.type == "Update your status")
                {
                    string updateMessage = "2@" + id + '@' + password + '@';
                    SendMessage(client, updateMessage, senderIpEp);
                }

                else if(serverMessage.type == "CompetitionEnded")
                {
                    string message = " Competition ended";

                    if (this.GiveGeneralMessageToJudgeForm != null)
                    {
                        this.GiveGeneralMessageToJudgeForm(this, message);
                    }
                }
                else if (serverMessage.type == "CompetitionStarted")
                {
                    string message = " Competition started";

                    if (this.GiveGeneralMessageToJudgeForm != null)
                    {
                        this.GiveGeneralMessageToJudgeForm(this, message);
                    }
                }
            }
        }

        public void GettingAndHandlingMessagesAdmin()
        {
            string oldContestant = "";
            while (isAdmin)
            {
                IPEndPoint senderIpEp = new IPEndPoint(IPAddress.Any, 0);
                Message serverMessage = new Message(GetMessage(client, ref senderIpEp), senderIpEp); //Here the listening gets called

                if (serverMessage.type == "Update your status")
                {
                    string updateMessage = "2@" + id + '@' + password + '@';
                    SendMessage(client, updateMessage, senderIpEp);
                }
                else if (serverMessage.type == "RequestJumpCodeFor")
                {
                    if (GiveActiveJumpToAdminForm != null)
                    {
                        String[] split = serverMessage.message.Split('&');

                        GiveActiveJumpToAdminForm(this, split[0], split[1], split[2], split[3]);

                        if (oldContestant != split[0])
                        {
                            string newContestantMessage = "Contestant " + split[0] + " jump number " + split[2];
                            if (GiveNewContestantMessageToAdminForm != null)
                            {
                                GiveNewContestantMessageToAdminForm(this, newContestantMessage);
                            }
                        }
                        oldContestant = split[0];
                    }
                }
                //Handle that the server tells the admin that a certain JUDGE has returned a SCORE
                else if (serverMessage.type == "SCORE")
                {
                    string scoreActivityMessage = "Score " + serverMessage.message + " was recieved from judge " + serverMessage.judge.username;

                    if (UpdateJudgeTextBoxGreen != null)
                    {
                        UpdateJudgeTextBoxGreen(this, serverMessage.judge.username + "@" + serverMessage.message);
                    }


                    if (GiveMessageToAdminForm != null)
                    {
                        GiveMessageToAdminForm(this, scoreActivityMessage);
                    }
                }
                else if (serverMessage.type == "ConnectedJudges")
                {
                    List<JudgeSerializable> jList = DeserializeConnectedJudges(serverMessage.message);

                    if (RefreshConnectedJudges != null)
                        RefreshConnectedJudges(this, jList);
                }
                else if (serverMessage.type == "AllFinishedContests") 
                {
                    List<string> finishedContests = new List<string>();
                    finishedContests = Deserialize<List<string>>(serverMessage.message);
                    if (GiveFinishedContestsToAdminForm != null)
                    {
                        GiveFinishedContestsToAdminForm(this, finishedContests);
                    }
                }
                else if (serverMessage.type == "JudgeOffline")
                {
                    if (UpdateJudgeTextBoxRed != null)
                    {
                        UpdateJudgeTextBoxRed(this, serverMessage.message);
                    }
                }
                else if (serverMessage.type == "JudgeOnline")
                {
                    if (UpdateJudgeTextBoxWhite != null)
                    {
                        UpdateJudgeTextBoxWhite(this, serverMessage.message);
                    }
                }

                else if (serverMessage.type == "ContestResult")
                {
                    //Hantera sträng av contestpacket?? i xml format och skicka till vyn
                    ContestPacket contestPacket = new ContestPacket();
                    contestPacket = Deserialize<ContestPacket>(serverMessage.message);
                    if (GiveContestPacketToAdminForm != null)
                    {
                        GiveContestPacketToAdminForm(this, contestPacket);
                    }
                }

                //Other general unpackeged log-message
                else if (serverMessage.type == "CompetitionEnded")
                {
                    if (ResetEndedCompetition != null)
                    {
                        ResetEndedCompetition(this, new EventArgs());
                    }
                }

                else if (serverMessage.type == "ContestPacket")
                {
                    activeContest = Deserialize<ContestPacket>(serverMessage.message);
                    if (GiveActiveContestPacketToAdminForm != null)
                    {
                        GiveActiveContestPacketToAdminForm(this, activeContest);
                    }

                    //Start contest
                    SendMessage(client, "4@" + id + "@7", serverStandardIpEp);
                }

                else if (serverMessage.type == "CompetitionStarted")
                {
                    string message = " Competition started";

                    if (this.GiveMessageToAdminForm != null)
                    {
                        this.GiveMessageToAdminForm(this, message);
                    }
                }
            }
        }

        /// <summary>
        /// Generic deseralize method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input"></param>
        /// <returns></returns>
        public T Deserialize<T>(string input)
        where T : class
        {
            System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(T));

            using (StringReader sr = new StringReader(input))
                return (T)ser.Deserialize(sr);
        }
        /// <summary>
        /// Generic serialize method.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="toSerialize"></param>
        /// <returns></returns>
        public String Serialize<T>(T toSerialize)
        where T : class
        {
            string xmlString;
            XmlSerializer writer = new XmlSerializer(toSerialize.GetType());
            using (System.IO.StringWriter file = new System.IO.StringWriter())
            {
                writer.Serialize(file, toSerialize);
                xmlString = file.ToString();
            }
            return xmlString;
        }

        public List<JudgeSerializable> DeserializeConnectedJudges(string sJudges)
        {
            List<JudgeSerializable> jList = new List<JudgeSerializable>();
            try
            {
                XmlSerializer reader = new XmlSerializer(jList.GetType());
                using (StringReader strReader = new StringReader(sJudges))
                {
                    jList = (List<JudgeSerializable>)reader.Deserialize(strReader);
                    strReader.Close();
                }
            }
            catch (Exception err)
            {
                throw err;
            }
            return jList;
        }
    }   
}
